/** 
    ____                            
   / __ )_________ __   _____  _____
  / __  / ___/ __ `/ | / / _ \/ ___/
 / /_/ / /  / /_/ /| |/ /  __/ /    
/_____/_/   \__,_/ |___/\___/_/     
* @Title: SimpleStringCache.java 
* @Package soft.ay.qram 
* @Description: String 类型key-value缓存
* @author vsuns 
* @date 2019年5月25日 
* @version V1.0 
*/
package soft.ay.qram;

import static com.google.common.base.Preconditions.checkNotNull;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**  
*  
*/
public class SimpleSyncStringCacheLoader implements ICacheLoader
{
    // 属性部分
    // ------------------分割线------------------

    /**
     * @Title: addCache @Description: 公共add方法 @param @param key @param @param
     *         value 参数 @return void 返回类型 @throws
     */
    private void addCache(String key, String value, Long endTime, String insertSQL, String updateSQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        checkNotNull(value, "value is null");
        checkNotNull(insertSQL, "insertSQL is null");
        checkNotNull(updateSQL, "updateSQL is null");
        Connection connection = null;
        Statement statement = null;
        // 操作过的行数
        Integer affectedRows = null;
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            connection.setAutoCommit(false);
            statement = connection.createStatement();
            // 保证key唯一性，先更新后新增
            if (endTime == null)
            {
                // 无失效限制
                affectedRows = statement.executeUpdate(String.format(updateSQL, value, key));
                if (affectedRows == 0)
                {// 没有任何数据，则新增
                    affectedRows = statement.executeUpdate(String.format(insertSQL, key, value));
                }
                 //要把expire里的cache删了
                statement.executeUpdate(String.format(DELETE_FROM_KV_TIME, key));
            }
            else
            {
                affectedRows = statement.executeUpdate(String.format(updateSQL, value, endTime, key));
                if (affectedRows == 0)
                {// 没有任何数据，则新增
                    affectedRows = statement.executeUpdate(String.format(insertSQL, key, value, endTime));
                }
                // 要把per里的cache删了
                statement.executeUpdate(String.format(DELETE_FROM_KV, key));
            }
            connection.commit();
            checkNotNull(affectedRows, "operation to db failed");
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        }

    }// endmethod

    // 方法部分
    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @param value
     * 
     * @see soft.ay.qram.CacheLoader#addToCache(java.lang.String,
     * java.lang.String)
     */

    @Override
    public void addToCache(String key, String value)
    {
        addCache(key, value, null, INSERT_TO_KV, UPDATE_TO_KV);
        key = null;
        value = null;
    }

    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @param value
     * 
     * @param expire
     * 
     * @see soft.ay.qram.CacheLoader#addToCache(java.lang.String,
     * java.lang.String, java.lang.Long)
     */

    @Override
    public void addToCache(String key, String value, TimeUnit unit, Long expire)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        Long endTime = System.currentTimeMillis();
        if (expire != null)
        {
            endTime += TimeUnit.MILLISECONDS.convert(expire, unit);
        }
        // 逻辑处理开始的地方
        addCache(key, value, endTime, INSERT_TO_KV_TIME, UPDATE_TO_KV_TIME);
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        key = null;
        value = null;
        expire = null;
    }

    private String getCache(String key, Long endTime, String selectSQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        checkNotNull(selectSQL, "selectSQL is null");
        Connection connection = null;
        Statement statement = null;
        ResultSet result = null;
        String stringValue = null;
        // 操作过的行数
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            statement = connection.createStatement();
            // 保证key唯一性，先更新后新增
            if (endTime == null)
            {
                // 无失效限制
                result = statement.executeQuery(String.format(selectSQL, key));
            }
            else
            {
                result = statement.executeQuery(String.format(selectSQL, key, endTime));
            }
            if (result.next())
            {
                stringValue = result.getString(1);
            }
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (result != null)
            {
                try
                {
                    result.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        } // endmethod

        return stringValue;

    }

    @Override
    public String getFromCache(String key)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方

        // 逻辑处理开始的地方
        checkNotNull(key, "key is null");
        String cacheValue = getCache(key, null, SELECT_FROM_KV);
        if (cacheValue==null)
        {
            cacheValue = getCache(key, System.currentTimeMillis(), SELECT_FROM_KV_TIME);
            if (cacheValue == null)
            { // 如果没有值，可能是已经过期了，需要把cache删了
                removeCache(null, DELETE_EXPIRE_FROM_KV_TIME);
            }
        }
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        key = null;
        return cacheValue;
    }

    private Set<String> getCacheKeys(Long endTime, String selectKeySQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(selectKeySQL, "selectKeySQL is null");
        Connection connection = null;
        Statement statement = null;
        ResultSet result = null;
        Set<String> keys = null;
        // 操作过的行数
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            statement = connection.createStatement();
            // 保证key唯一性，先更新后新增
            if (endTime == null)
            {
                // 无失效限制
                result = statement.executeQuery(selectKeySQL);
            }
            else
            {
                result = statement.executeQuery(String.format(selectKeySQL, endTime));
            }

            while (result.next())
            {
                if (keys == null)
                {
                    keys = new HashSet<>(result.getFetchSize());
                }
                keys.add(result.getString(1));
            }
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (result != null)
            {
                try
                {
                    result.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        } // endmethod

        return keys;

    }
    /*
     * (非 Javadoc) *
     * 
     * @return
     * 
     * @see soft.ay.qram.ICacheLoader#getKeys()
     */

    @Override
    public Set<String> getKeys()
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        Set<String> keys = new HashSet<>();
        Set<String> cacheKeys = null;
        // 逻辑处理开始的地方
        cacheKeys = getCacheKeys(null, SELECT_KEYS_FROM_KV);
        if (cacheKeys != null)
        {
            keys.addAll(cacheKeys);
        }
        cacheKeys = getCacheKeys(System.currentTimeMillis(), SELECT_KEYS_FROM_KV_TIME);
        if (cacheKeys != null)
        {
            keys.addAll(cacheKeys);
        }
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        return keys;
    }

    private Integer countCacheKey(String key, Long endTime, String countKeySQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(countKeySQL, "countKeySQL is null");
        Connection connection = null;
        Statement statement = null;
        ResultSet result = null;
        Integer count = null;
        // 操作过的行数
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            statement = connection.createStatement();
            // 保证key唯一性，先更新后新增
            if (endTime == null)
            {
                // 无失效限制
                result = statement.executeQuery(String.format(countKeySQL, key));
            }
            else
            {
                result = statement.executeQuery(String.format(countKeySQL, key, endTime));
            }

            if (result.next())
            {
                count = result.getInt(1);
            }
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (result != null)
            {
                try
                {
                    result.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        } // endmethod

        return count;

    }

    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @return
     * 
     * @see soft.ay.qram.ICacheLoader#containKey(java.lang.String)
     */

    @Override
    public Boolean containKey(String key)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        // 逻辑处理开始的地方
        Integer counter = countCacheKey(key, null, COUNT_KEYS_FROM_KV);
        if (counter == null || counter.intValue() == 0)
        {
            counter = countCacheKey(key, System.currentTimeMillis(), COUNT_KEYS_FROM_KV_TIME);
            if (counter == null)
            { // 如果没有值，可能是已经过期了，需要把cache删了
                removeCache(null, DELETE_EXPIRE_FROM_KV_TIME);
            }
        }
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        key = null;
        return counter.intValue() > 0;
    }


    private void removeCache(String key, String deleteSQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(deleteSQL, "deleteSQL is null");
        Connection connection = null;
        Statement statement = null;
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            statement = connection.createStatement();
            if (key == null)
            {// 删除已经过期的key
                statement.executeUpdate(String.format(deleteSQL, System.currentTimeMillis()));
            }
            else
            {
                // 删除
                statement.executeUpdate(String.format(deleteSQL, key));
            }
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        } // endmethod
    }

    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @see soft.ay.qram.ICacheLoader#removeKey(java.lang.String)
     */

    @Override
    public void removeKey(String key)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        // 逻辑处理开始的地方
        removeCache(key, DELETE_FROM_KV);
        removeCache(key, DELETE_FROM_KV_TIME);
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        key = null;
    }

    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @param unit
     * 
     * @param expire
     * 
     * @see soft.ay.qram.ICacheLoader#expireKey(java.lang.String,
     * java.util.concurrent.TimeUnit, java.lang.Long)
     */

    @Override
    public void expireKey(String key, TimeUnit unit, Long expire)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        checkNotNull(unit, "unit is null");
        checkNotNull(expire, "expire is null");
        // 逻辑处理开始的地方
        // 1.从per中找cache
        String cacheValue = getCache(key, null, SELECT_FROM_KV);
        if (cacheValue == null)
        {
            // 2.从expire中找cache
            cacheValue = getCache(key, 0L, SELECT_FROM_KV_TIME);
            checkNotNull(cacheValue, "cache of key doesn't exist");
        }
        // 3.更新key，或添加cache到expire
        Long endTime = System.currentTimeMillis();
        if (expire != null)
        {
            endTime += TimeUnit.MILLISECONDS.convert(expire, unit);
        }
        addCache(key, cacheValue, endTime, INSERT_TO_KV_TIME, UPDATE_TO_KV_TIME);
        // 4.无论如何都需要删除per中的cache
        removeCache(key, DELETE_FROM_KV);
        // 逻辑处理结束的地方


        // 局部变量销毁的地方
        key = null;
        expire = null;
    }

    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @param unit
     * 
     * @param expire
     * 
     * @see soft.ay.qram.ICacheLoader#persistKey(java.lang.String,
     * java.util.concurrent.TimeUnit, java.lang.Long)
     */

    @Override
    public void persistKey(String key)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        // 逻辑处理开始的地方
        // 1.从per中找cache
        String cacheValue = getCache(key, null, SELECT_FROM_KV);
        if (cacheValue == null)
        {
            // 2.从expire中找cache
            cacheValue = getCache(key, 0L, SELECT_FROM_KV_TIME);
            checkNotNull(cacheValue, "cache of key doesn't exist");
            // 3.更新key，或添加cache到expire
            addCache(key, cacheValue, null, INSERT_TO_KV, UPDATE_TO_KV);
            // 4.无论如何都需要删除expire中的cache
            removeCache(key, DELETE_FROM_KV_TIME);
        }
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        key = null;
    }
    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @return
     * 
     * @see soft.ay.qram.ICacheLoader#matchKey(java.lang.String)
     */

    @Override
    public Set<String> matchKey(String key)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        Set<String> keys = new HashSet<>();
        Set<String> cacheKeys = null;
        // 逻辑处理开始的地方
        cacheKeys = matchCacheKeys(key, null, MATCH_KEYS_FROM_KV);
        if (cacheKeys != null)
        {
            keys.addAll(cacheKeys);
        }
        cacheKeys = matchCacheKeys(key, System.currentTimeMillis(), MATCH_KEYS_FROM_KV_TIME);
        if (cacheKeys != null)
        {
            keys.addAll(cacheKeys);
        }
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        return keys;
    }

    private Set<String> matchCacheKeys(String key, Long endTime, String matchKeySQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(matchKeySQL, "matchKeySQL is null");
        Connection connection = null;
        Statement statement = null;
        ResultSet result = null;
        Set<String> keys = null;
        // 操作过的行数
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            statement = connection.createStatement();
            // 保证key唯一性，先更新后新增
            if (endTime == null)
            {
                // 无失效限制
                result = statement.executeQuery(String.format(matchKeySQL, key + "%"));
            }
            else
            {
                result = statement.executeQuery(String.format(matchKeySQL, endTime, key + "%"));
            }

            while (result.next())
            {
                if (keys == null)
                {
                    keys = new HashSet<>(result.getFetchSize());
                }
                keys.add(result.getString(1));
            }
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (result != null)
            {
                try
                {
                    result.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        } // endmethod

        return keys;

    }
    /*
     * (非 Javadoc) *
     * 
     * @param key
     * 
     * @return
     * 
     * @see soft.ay.qram.ICacheLoader#getFromCacheByMatchKey(java.lang.String)
     */

    @Override
    public Map<String, String> getFromCacheByMatchKey(String key)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方

        // 逻辑处理开始的地方
        checkNotNull(key, "key is null");
        Map<String, String> cacheValue = getCacheByMatchKey(key, null, SELECT_FROM_KV_BY_MATCH_KEY);
        cacheValue.putAll(getCacheByMatchKey(key, System.currentTimeMillis(), SELECT_FROM_KV_TIME_BY_MATCH_KEY));
        removeCache(null, DELETE_EXPIRE_FROM_KV_TIME);
        // 逻辑处理结束的地方

        // 局部变量销毁的地方
        key = null;
        return cacheValue.isEmpty() ? null : cacheValue;
    }

    private Map<String, String> getCacheByMatchKey(String key, Long endTime, String matchSQL)
    {
        // 注意：使用控制语句时，将头尾位置用注释标记起来
        // 局部变量定义的地方
        checkNotNull(key, "key is null");
        checkNotNull(matchSQL, "matchSQL is null");
        Connection connection = null;
        Statement statement = null;
        ResultSet result = null;
        Map<String, String> stringKeyValue = null;
        // 操作过的行数
        // 逻辑处理开始的地方
        try
        {
            connection = DataSource.getInstance().getConnection();
            statement = connection.createStatement();
            // 保证key唯一性，先更新后新增
            if (endTime == null)
            {
                // 无失效限制
                result = statement.executeQuery(String.format(matchSQL, key + "%"));
            }
            else
            {
                result = statement.executeQuery(String.format(matchSQL, key + "%", endTime));
            }
            stringKeyValue = new HashMap<>();
            while (result.next())
            {
                stringKeyValue.put(result.getString(1), result.getString(2));
            }
            // 逻辑处理结束的地方
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 局部变量销毁的地方
            if (result != null)
            {
                try
                {
                    result.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }
            if (statement != null)
            {
                try
                {
                    statement.closeOnCompletion();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            } // endif
            if (connection != null)
            {
                try
                {
                    connection.close();
                }
                catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }

        } // endmethod

        return stringKeyValue;

    }

}
